﻿using Basic.ScheduledTask;
using Basic.ScheduledTask.Abstract;
using Basic.ScheduledTask.HangfireImplement;
using FluentScheduler;
using Hangfire;
using Hangfire.Dashboard;
using Hangfire.MemoryStorage;
using Hangfire.PostgreSql;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using System.Reflection;

namespace System
{
    public static class JobExtension
    {

        public static IServiceCollection RegisterScheduledTask(this IServiceCollection services, IConfigurationSection section, params Assembly[] assemblies)
        {
            services.AddJobManager(section);

            if (assemblies?.Length > 0)
            {
                Type baseType = typeof(ScheduledBackgroundService);
                Type interfaceType = typeof(IHostedService);
                foreach (var assembly in assemblies)
                {
                    var publicTypes = assembly.GetExportedTypes();
                    var scheduleTaskTypes = publicTypes.Where(s => s.BaseType.GUID == baseType.GUID).ToList();
                    foreach (var taskType in scheduleTaskTypes)
                        services.TryAddEnumerable(ServiceDescriptor.Describe(interfaceType, taskType, ServiceLifetime.Singleton));
                }
            }

            return services;
        }

        /// <summary>
        /// 添加定时任务管理器
        /// </summary>
        /// <param name="services"></param>
        /// <param name="section"></param>
        /// <returns></returns>
        public static IServiceCollection AddJobManager(this IServiceCollection services, IConfigurationSection section)
        {
            services.Configure<JobDbConfig>(section);
            services.AddSingleton<IJobManager, HangfireJobManager>();
            var conf = section.Get<JobDbConfig>();
            string dbType = conf?.DbType?.ToLower();
            if (string.Equals(dbType, "sqlserver") || string.Equals(dbType, "mssql"))
                services.AddHangfire(configuration => configuration
                    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
                    .UseSimpleAssemblyNameTypeSerializer()
                    .UseRecommendedSerializerSettings()
                    .UseSqlServerStorage(conf.ConnetionString));
            else if (string.Equals(dbType, "postgresql"))
                services.AddHangfire(configuration => configuration
                    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
                    .UseSimpleAssemblyNameTypeSerializer()
                    .UseRecommendedSerializerSettings()
                    .UsePostgreSqlStorage(pg =>
                    {
                        pg.UseNpgsqlConnection(conf.ConnetionString);
                    }));
            else
                services.AddHangfire(configuration => configuration
                    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
                    .UseSimpleAssemblyNameTypeSerializer()
                    .UseRecommendedSerializerSettings()
                    .UseMemoryStorage(new MemoryStorageOptions
                    {
                        JobExpirationCheckInterval = TimeSpan.FromMinutes(30),
                        CountersAggregateInterval = TimeSpan.FromMinutes(3.0),
                        FetchNextJobTimeout = TimeSpan.FromMinutes(30.0)
                    }, new Hangfire.MemoryStorage.Database.Data()));

            services.AddHangfireServer();
            services.AddSingleton<JobStaticManager>();

            return services;
        }

        public static IApplicationBuilder UseJobManager(this IApplicationBuilder app)
        {
            app.UseHangfireDashboard("/hangfire", new DashboardOptions
            {
                IsReadOnlyFunc = (DashboardContext context) => true
            });

            var manager = app.ApplicationServices.GetService<JobStaticManager>();
            return app;
        }


        #region FluentScheduler注册器

        /// <summary>
        /// 注入FluentScheduler，配合UseFluentScheduler()使用
        /// </summary>
        /// <param name="services"></param>
        /// <param name="assemblies"></param>
        public static IServiceCollection AddFluentSchedulerSetup(this IServiceCollection services, params Assembly[] assemblies)
        {
            List<Type> registryTypes = new List<Type>();
            List<Type> jobTypes = new List<Type>();

            Type baseType = typeof(Registry);
            Type jobType = typeof(IJob);

            //程序集中实现了Registry的类
            foreach (var assembly in assemblies)
            {
                var collection = assembly.GetTypes().Where(t => !t.IsInterface && !t.IsAbstract);

                var registrys = collection.Where(t => t.BaseType?.GUID == baseType.GUID).ToArray();
                if (registrys?.Length > 0) registryTypes.AddRange(registrys);

                var jobList = collection.Where(s => s.GetInterfaces()?.Contains(jobType) == true).ToArray();
                if (jobList.Length > 0) jobTypes.AddRange(jobList);
            }
            // 注册IJob,
            foreach (var type in registryTypes)
                services.AddSingleton(baseType, type);

            // IJob
            foreach (var type in jobTypes)
                services.AddSingleton(type);

            return services;
        }

        /// <summary>
        /// 注册FluentSchedulerJobFactory
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseFluentScheduler(this IApplicationBuilder app)
        {
            FluentSchedulerJobFactory factory = new FluentSchedulerJobFactory(app.ApplicationServices);
            JobManager.JobFactory = factory;

            Registry[]? registryList = app.ApplicationServices.GetServices<Registry>()?.ToArray();
            JobManager.Initialize(registryList);

            return app;
        }

        #endregion
    }

}